// scalaxx - Scala XML eXtensions
// (c) 2009, Normen Müller, normen.mueller@gmail.com
// $Id: Parser.scala 50 2009-11-12 11:37:31Z normen.mueller $
package scalaxx.path

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.syntactical._

/** cf. http://www.w3.org/TR/xpath 
 * @todo Whitespaces may be freely added within patterns before or after any Token.
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [path] class Parser extends StdTokenParsers with ImplicitConversions {
  /* Fill in abstract defs */
  type Tokens = Lexer 
  val lexical: Tokens = new Tokens
  
  /* Configure lexical parsing */
  lexical.delimiters ++= List("(", ")", "[", "]", "," , "/", "//")
  
  /* Define the grammar */
  lazy val expr: Parser[Expr] = orExpr
  
  private lazy val orExpr  = chainl1(andExpr,  op("or") ^^ {x => Or(_: Expr, _: Expr)})
  
  private lazy val andExpr = chainl1(eqExpr, op("and") ^^ {x => And(_: Expr, _: Expr)})
  
  private lazy val eqExpr  = relExpr ~ rep((op("=") | op("!=")) ~ relExpr) ^^ {
    case x ~ xs => xs.foldLeft(x){(_, _) match {
      case (a, "="~b) => Equal(a, b)
      case (a, "!="~b) => Unequal(a, b)
    }}}
  
  private lazy val relExpr = addExpr ~ rep((op("<=") | op("<") | op(">=") | op(">")) ~ addExpr) ^^ {
    case x ~ xs => xs.foldLeft(x){(_, _) match {
      case (a, "<="~b) => LTE(a, b)
      case (a, "<"~b)  => LT(a, b)
      case (a, ">="~b) => GTE(a, b)
      case (a, ">"~b)  => GT(a, b)
    }}}
  
  private lazy val addExpr = mulExpr ~ rep((op("+") | op("-")) ~ mulExpr) ^^ {
    case x ~ xs => xs.foldLeft(x){(_, _) match {
      case (a, "+"~b) => Plus(a, b)
      case (a, "-"~b) => Minus(a, b)
    }}}
  
  private lazy val mulExpr = unaExpr ~ rep((op("*") | op("div") | op("mod")) ~ unaExpr) ^^ {
    case x ~ xs => xs.foldLeft(x){(_, _) match {
      case (a, "*"~b) => Mult(a, b)
      case (a, "div"~b) => Div(a, b)
      case (a, "mod"~b) => Mod(a, b)
    }}}
  
  private lazy val unaExpr = op("-") ~> uniExpr ^^ {Negative(_)} | uniExpr
  
  private lazy val uniExpr = patExpr ~ rep(op("|") ~> patExpr) ^^ {
    case x ~ xs => xs.foldLeft(x){(_, _) match {
      case (a, b) => Union(a, b)
    }}
  }
  
  //private lazy val patExpr = locPath | filExpr ~ opt(op("/") ~> relPath) ^^ {case f~None => f case f~Some(p) => Path(f,p)}
  private lazy val patExpr = locPath | filExpr ~ opt(op("/") ~> relPath) ^^ {case f~None => f case f~Some(p) => Path(f :: p :: Nil)}
  
  private lazy val locPath = absPath | relPath
  
  //private lazy val absPath = op("/") ~> opt(relPath) ^^ {case Some(Path(s@_*)) => Root(s:_*) case None => Root()}
  private lazy val absPath = op("/") ~> opt(relPath) ^^ {case Some(Path(s)) => Root(s) case None => Root}
  
  //private lazy val relPath = step ~ rep(op("/") ~> step) ^^ {case s~Nil => Path(s) case s~ss  => Path(s::ss:_*)}
  private lazy val relPath = step ~ rep(op("/") ~> step) ^^ {case s~ss  => Path(s::ss)}
  
  //private lazy val step    = axis ~ pred ^^ {case ax~Nil => ax case Axis(n,i)~ps => Axis(n,i,ps:_*)}
  private lazy val step: Parser[Expr] = axis ~ pred ^^ {case Axis(n,i,as)~ps => Axis(n,i,as :::ps)}
  
  private lazy val pred    = rep(keyword("[") ~> expr <~ keyword("]"))
  
  private lazy val filExpr = priExpr ~ pred ^^ {case pe~Nil => pe case pe~ps  => Filter(pe,ps)}
  
  private lazy val priExpr = funCall | literal | number | varref | keyword("(") ~> expr <~ keyword(")")
  
  private lazy val funCall = fun ~ opt(args) <~ ")" ^^ {case x~Some(y) => Fun(x,y) case x~None => Fun(x, Nil)}
  
  private lazy val args    = expr ~ rep(keyword(",") ~> expr) ^^ {case x~y => x :: y}
  
  import scalaxx.path.Lexer;
  import lexical._
  private lazy val fun = accept("func", {case FunTok(n) => n.chars})
  
  private lazy val varref = accept("varref", {case VarRefTok(n) => $(n.chars)})
  
  private def op(o: String) = accept("op", {case OpTok(n) if n == o => n})
  
  private lazy val axis = accept("axis", {
    case AxisTok(n,TyTok(t))  => Axis(n,Type(t), Nil)
    case AxisTok(n,NCTok(l))  => Axis(n,NCName(l), Nil)
    case AxisTok(n,QTok(p,l)) => Axis(n,QName(p,l), Nil)
    case AxisTok(n,AllTok)    => Axis(n,All, Nil)
  })  
  
  private lazy val literal = accept("string", {case StringLit(n)  => TVal(n)})
  
  private lazy val number = accept("numeric", {case NumericLit(n) => NVal(n.toDouble)})
}
